Skip to content

Initial commit of prototype converter for Rohde and Schwarz IQ.TAR #144

Open
KelseyCreekSoftware wants to merge 6 commits into
sigmf:mainfrom
KelseyCreekSoftware:feature/rohdeschwarz
Open

Initial commit of prototype converter for Rohde and Schwarz IQ.TAR #144
KelseyCreekSoftware wants to merge 6 commits into
sigmf:mainfrom
KelseyCreekSoftware:feature/rohdeschwarz

Conversation

@KelseyCreekSoftware
Copy link
Copy Markdown
Contributor

@codacy-production
Copy link
Copy Markdown

codacy-production Bot commented Apr 4, 2026

Not up to standards ⛔

🔴 Issues 17 high

Alerts:
⚠ 17 issues (≤ 0 issues of at least minor severity)

Results:
17 new issues

Category Results
Security 17 high

View in Codacy

🟢 Metrics 90 complexity · 5 duplication

Metric Results
Complexity 90
Duplication 5

View in Codacy

TIP This summary will be updated as you push new changes. Give us feedback

@Teque5
Copy link
Copy Markdown
Collaborator

Teque5 commented Apr 8, 2026

Before we get going on this Rohde & Swarz support, is pr #136 ready to merge? If so let's do that and then you can rebase this branch.

@KelseyCreekSoftware
Copy link
Copy Markdown
Contributor Author

Sure, that sounds like a good plan.
I think the Spike Signal Hound branch is ready to merge, but I'm just learning about the SigMF python library, so would defer to others.

@Teque5
Copy link
Copy Markdown
Collaborator

Teque5 commented Apr 14, 2026

I merged the Signal Hound branch so you should be able to rebase this branch to main now.

Comment thread sigmf/convert/__main__.py
from . import detect_converter
from .blue import blue_to_sigmf
from .signalhound import signalhound_to_sigmf
from .signalhound import signalhound_to_sigmf
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

repeated line

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an initial Rohde & Schwarz IQ.TAR → SigMF conversion path to the sigmf.convert subsystem, wiring it into converter auto-detection and the sigmf_convert CLI, and introducing tests to validate key behaviors.

Changes:

  • Introduce sigmf/convert/rohdeschwarz.py implementing IQ.TAR extraction, XML parsing/validation, SigMF metadata construction, and dataset/archive/NCD writing.
  • Extend converter detection + CLI dispatch to recognize .tar IQ.TAR recordings and invoke the new converter.
  • Add unit tests covering XML-to-dict conversion, TAR extraction, validation failures, metadata building, IQ reading, and NCD creation.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 15 comments.

File Description
sigmf/convert/rohdeschwarz.py New Rohde & Schwarz IQ.TAR converter implementation (extraction, validation, metadata + data conversion, output writing).
sigmf/convert/__init__.py Extend converter detection to recognize Rohde & Schwarz IQ.TAR and enhance magic-byte lookup.
sigmf/convert/__main__.py Wire the new converter into the unified conversion CLI.
tests/test_convert_rohdeschwarz.py New test suite for the Rohde & Schwarz converter.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +71 to +75
member_path = os.path.join(target_dir, member.name)
abs_target = os.path.abspath(target_dir)
abs_member = os.path.abspath(member_path)

return abs_member.startswith(abs_target)
Comment on lines +77 to +85
def safe_extract(tar, target_dir):
"""
Extract only safe members from a tarfile.
"""
for member in tar.getmembers():
if not is_safe_member(tar, member, target_dir):
raise Exception(f"Unsafe path detected in TAR: {member.name}")
tar.extract(member, target_dir)

Comment on lines +126 to +142
# validate CenterFrequency
center_freq_raw = _text_of(root, "Clock")
try:
center_frequency = float(center_freq_raw)
except (TypeError, ValueError) as err:
raise SigMFConversionError(f"Invalid or missing CenterFrequency: {center_freq_raw}") from err

# validate SampleRate
num_samples_raw = _text_of(root, "Samples")
try:
sample_rate = float(num_samples_raw)
except (TypeError, ValueError) as err:
raise SigMFConversionError(f"Invalid or missing SampleRate: {num_samples_raw}") from err

if sample_rate <= 0:
raise SigMFConversionError(f"Invalid SampleRate: {sample_rate} (must be > 0)")

Comment on lines +338 to +347
# add optional rohdeschwarz-specific fields to global metadata using rohdeschwarz: namespace
# only include fields that aren't already represented in standard SigMF metadata
if scaling_factor:
global_md["rohdeschwarz:scaling_factor"] = scaling_factor
if datafilename:
global_md["rohdeschwarz:iq_datafilename"] = datafilename # provenance
if userdata:
global_md["rohdeschwarz:userdata"] = userdata #open ended field for user defined data.
if preview_data:
global_md["rohdeschwarz:preview_trace"] = preview_data
Comment on lines +468 to +470
# Get unique IQ filename from global_info
iq_filename = global_info.get("rohdeschwarz:iq_datafilename")
print(f"iq_filename: {iq_filename}")
Comment thread sigmf/convert/__init__.py
Comment on lines +104 to +115
elif file_path.suffix in [".tar"]:
# iq.tar file extensions are used by Rohde & Schwarz for their IQ data, but the .tar extension is also used by other formats.
# So parse the tar file to determine if it is a Rohde & Schwarz file or not.
rohde_schwarz_expanded_magic_bytes = get_magic_bytes(file_path, count=20, offset=0,magic_bytes=b"RS_IQ_TAR_FileFormat") # <RS_IQ_TAR_FileFormat>
if rohde_schwarz_expanded_magic_bytes == b"RS_IQ_TAR_FileFormat":
return "rohdeschwarz"
else:
raise SigMFConversionError(
f"Unsupported XML file format. Root element: {rohde_schwarz_expanded_magic_bytes}. "
f"Expected RS_IQ_TAR_FileFormat for IQ.TAR files."
)

Comment on lines +27 to +29
"""
Create a minimal, valid Rohde & Schwarz IQ.TAR file for testing.
"""
Comment on lines +553 to +554
data_file_path = rohdeschwarz_path.parent / iq_filename
print(f"data_file_path: {data_file_path}")
Comment on lines +542 to +545
output_dir = filenames["archive_fn"].parent
output_dir.mkdir(parents=True, exist_ok=True)
meta.tofile(filenames["archive_fn"], toarchive=True)
log.info("wrote SigMF archive to %s", filenames["archive_fn"])
Comment on lines +581 to +583
# write metadata file
meta.tofile(filenames["meta_fn"], toarchive=False)
log.info("wrote SigMF metadata to %s", filenames["meta_fn"])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants